<template>
  <div class="trs-scroll-box" @scroll="onScroll">
    <div
      v-for="(page, pIdx) in layout.pdf_info"
      :key="pIdx"
      :ref="`pageRef${pIdx}`"
      class="page"
      :id="`trs-page-${pIdx + 1}`"
      :data-page-idx="pIdx"
      :style="pageStyle(page)">
      <!-- 文字 / 标题 -->
      <div
        :id="`trs-${pIdx + 1}-${getBIndex(blk)}`"
        v-for="(blk, bIdx) in blocksOf(page, ['text', 'title'])"
        :key="`t-${pIdx}-${bIdx}`"
        :class="['text-block', blk.type === 'title' ? 'fw' : '']"
        :style="blockStyle(blk)">
        <!-- <span style="position: absolute;top: 0;left: 0;transform: translate(-50%,-50%);background: red;">{{
          getBIndex(blk) }}</span> -->
        <div :style="textStyle(blk)"
          :class="['inline-wrapper', currentId === `trs-${pIdx + 1}-${getBIndex(blk)}` ? 'active_box' : '']"
          @click="blkClick(`trs-${pIdx + 1}-${getBIndex(blk)}`)">
          <span
            v-for="(node, nIdx) in renderBlock(blk)"
            :key="nIdx"
            v-bind="node.attrs"
            v-html="node.html" />
        </div>
      </div>

      <!-- 行间公式 -->
      <div
        :id="`trs-${pIdx + 1}-${getBIndex(blk)}`"

        v-for="(blk, bIdx) in blocksOf(page, ['interline_equation'])"
        :key="`eq-${pIdx}-${bIdx}`"
        class="scale-wrapper"
        :style="blockStyle(blk)">
        <div
          :class="['scale-inner', currentId === `trs-${pIdx + 1}-${getBIndex(blk)}` ? 'active_box' : '']"
          @click="blkClick(`trs-${pIdx + 1}-${getBIndex(blk)}`)"
          v-scale-katex="{
            content: blk.lines[0].spans[0].content,
            imagePath: blk.lines[0].spans[0].image_path
          }" />
      </div>

      <!-- 图片 -->
      <div
        :id="`trs-${pIdx + 1}-${getBIndex(blk)}`"
        v-for="(blk, bIdx) in blocksOf(page, ['image'])"
        :key="`img-${pIdx}-${bIdx}`"
        class="scale-wrapper"
        :style="blockStyle(blk)">
        <img
          @click="blkClick(`trs-${pIdx + 1}-${getBIndex(blk)}`)"
          class="scale-inner"
          :src="blk.blocks[0].lines[0].spans[0].image_path"
          alt="figure"
          v-scale-img>
      </div>

      <!-- 图片 caption -->
      <div
        v-for="(blk, bIdx) in blocksOf(page, ['image_caption'])"
        :key="`icap-${pIdx}-${bIdx}`"
        class="text-block"
        :style="blockStyle(blk)">
        <div :style="textStyle(blk)" class="inline-wrapper">
          <span
            v-for="(node, nIdx) in renderBlock(blk)"
            :key="nIdx"
            v-bind="node.attrs"
            v-html="node.html" />
        </div>
      </div>

      <!-- 表格 -->
      <div
        :id="`trs-${pIdx + 1}-${getBIndex(blk)}`"
        v-for="(blk, bIdx) in blocksOf(page, ['table'])"
        :key="`tb-${pIdx}-${bIdx}`"
        class="scale-wrapper"
        :style="blockStyle(blk)">
        <div
          @click="blkClick(`trs-${pIdx + 1}-${getBIndex(blk)}`)"
          class="scale-inner"
          v-scale-table="blk.blocks[0].lines[0].spans[0].html" />
      </div>

      <!-- 表格 caption -->
      <div
        v-for="(blk, bIdx) in blocksOf(page, ['table_caption'])"
        :key="`tcap-${pIdx}-${bIdx}`"
        class="text-block"
        :style="blockStyle(blk)">
        <div :style="textStyle(blk)" class="inline-wrapper">
          <span
            v-for="(node, nIdx) in renderBlock(blk)"
            :key="nIdx"
            v-bind="node.attrs"
            v-html="node.html" />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import katex from 'katex'
import 'katex/dist/katex.min.css'

export default {
  name: 'PdfRenderer',
  props: {
    layout: { type: Object, default: () => ({}) },
    pageWidth: { type: Number, default: 800 },
  },
  data () {
    return {
      fontMap: new Map(),
      realHeightMap: new Map(),
      overflowMap: new Map(),
      cascadeOffsetMap: new Map(),
      minFS: 14,
      maxFS: 20,
      // pageWidth: 800,
      pageScale: 1,
      currentId: '',//选中块的id
      isProgrammaticScroll:false,//是否程序滚动-用户区分滚动监听中程序触发

    }
  },
  mounted () {
    this.$nextTick(() => this.calcAllFontSizes())
  },
  methods: {
    collectBlocks (paraBlocks, types, out = []) {
      paraBlocks.forEach(b => {
        if (types.includes(b.type)) out.push(b)
        if (b.blocks && Array.isArray(b.blocks)) {
          this.collectBlocks(b.blocks, types, out)
        }
      })
      return out
    },
    blocksOf (page, types) {
      return this.collectBlocks(page.para_blocks, types)
    },

    /* ✅ 动态高度核心 */
    calcPageContentHeight (page) {
      let maxBottom = 0
      const blocks = this.blocksOf(page, [
        'text', 'title', 'image_caption', 'table_caption',
        'image', 'interline_equation', 'table'
      ])

      blocks.forEach(blk => {
        const [, y, , y2] = blk.bbox
        const key = blk.bbox.join(',')
        const offset = this.cascadeOffsetMap.get(key) || 0
        const overflow = this.overflowMap.get(key) || 0          // ✅ 文字自身溢出
        const finalBottom = (y2 * this.pageScale) + offset + overflow
        maxBottom = Math.max(maxBottom, finalBottom)
      })

      return maxBottom + 20   // 留一点底边距
    },

    pageStyle (page) {
      const [w] = page.page_size
      this.pageScale = this.pageWidth / w
      const dynamicHeight = this.calcPageContentHeight(page)
      return {
        width: `${w * this.pageScale}px`,
        height: `${dynamicHeight}px`,
        position: 'relative',
        margin: '0 auto 20px',
        background: '#dfdfdf'
      }
    },

    blockStyle (blk) {
      const [x, y, x2, y2] = blk.bbox
      const key = blk.bbox.join(',')
      const baseTop = y * this.pageScale
      const cascadeOffset = this.cascadeOffsetMap.get(key) || 0
      return {
        position: 'absolute',
        left: `${x * this.pageScale}px`,
        top: `${baseTop + cascadeOffset}px`,
        width: `${(x2 - x + 5) * this.pageScale}px`,
        height: `${(y2 - y) * this.pageScale}px`
      }
    },

    textStyle (blk) {
      const fs = this.fontMap.get(blk.bbox.join(','))
      const scale = (blk.type === 'image_caption' || blk.type === 'table_caption') ? 0.85 : 1
      return {
        fontSize: `${fs * scale}px`,
        lineHeight: 1.15,
        whiteSpace: 'pre-wrap',
        wordBreak: 'break-word'
      }
    },

    renderBlock (blk) {
      const res = []
      blk.lines.forEach(line => {
        line.spans.forEach(span => {
          if (span.type === 'inline_equation') {
            res.push({
              html: katex.renderToString(span.content, { displayMode: false, throwOnError: false }),
              attrs: { class: 'katex-inline' }
            })
          } else {
            let t = span.content
              .replace(/([\u4e00-\u9fa5])\s+(?=[\u4e00-\u9fa5])/g, '$1')
              .replace(/ /g, '&nbsp;')
              .replace(/\n/g, '')
            res.push({ html: t, attrs: {} })
          }
        })
        // res.push({ html: '<br/>', attrs: {} })
      })
      return res
    },

    calcAllFontSizes () {
      this.layout.pdf_info.forEach(page => {
        this.blocksOf(page, ['text', 'title', 'image_caption', 'table_caption']).forEach(blk => {
          const key = blk.bbox.join(',')
          const maxH = (blk.bbox[3] - blk.bbox[1]) * this.pageScale * 0.95
          const maxW = (blk.bbox[2] - blk.bbox[0]) * this.pageScale
          const text = blk.lines.map(l => l.spans.map(s => s.content).join('')).join('')

          let min = this.minFS, max = blk.type === 'title' ? 40 : this.maxFS, best = min
          const cvs = document.createElement('canvas')
          const ctx = cvs.getContext('2d')
          for (let i = 0; i < 6; i++) {
            const mid = (min + max) / 2
            ctx.font = `${mid}px ${getComputedStyle(document.body).fontFamily || 'serif'}`
            let line = '', h = 0, lineH = mid * 1.15
              ;[...text].forEach(ch => {
                if (ctx.measureText(line + ch).width > maxW && line) { h += lineH; line = ch } else line += ch
              })
            h += lineH * 1.1
            h <= maxH ? ((best = mid), (min = mid)) : (max = mid)
          }
          this.fontMap.set(key, best)

          ctx.font = `${best}px ${getComputedStyle(document.body).fontFamily}`
          let line = '', h = 0, lineH = best * 1.15
            ;[...text].forEach(ch => {
              if (ctx.measureText(line + ch).width > maxW && line) { h += lineH; line = ch } else line += ch
            })
          h += lineH * 1.1
          this.realHeightMap.set(key, h)
          this.overflowMap.set(key, Math.max(0, h - maxH))
        })
      })

      this.calcCascadeOffset()
      this.$forceUpdate()

      // ✅ 动态刷新每页高度
      this.$nextTick(() => {
        this.layout.pdf_info.forEach((page, pIdx) => {
          const el = this.$el.querySelector(`.page[data-page-idx="${pIdx}"]`)
          if (el) {
            el.style.height = this.calcPageContentHeight(page) + 'px'
          }
        })
      })
    },

    calcCascadeOffset () {
      const offsetMap = new Map()
      const intersect = (a, b) => !(a.bbox[2] < b.bbox[0] || a.bbox[0] > b.bbox[2])

      this.layout.pdf_info.forEach(page => {
        const pageBlocks = this.blocksOf(page, [
          'text', 'title', 'image_caption', 'table_caption',
          'image', 'interline_equation', 'table'
        ])

        // 按 y 升序排列
        pageBlocks.sort((a, b) => a.bbox[1] - b.bbox[1])

        pageBlocks.forEach((blk, i) => {
          const key = blk.bbox.join(',')
          let totalOffset = 0

          // ✅ 判断是否是“页内最顶部块”
          const isPageTopmost = !pageBlocks.some(other => other.bbox[3] < blk.bbox[1])

          if (isPageTopmost) {
            offsetMap.set(key, 0)
            return
          }

          for (let j = 0; j < i; j++) {
            const above = pageBlocks[j]
            const aboveKey = above.bbox.join(',')
            const aboveOverflow = this.overflowMap.get(aboveKey) || 0
            const aboveOffset = offsetMap.get(aboveKey) || 0
            const aboveTotal = aboveOverflow + aboveOffset
            if (aboveTotal > 0 && intersect(blk, above)) {
              totalOffset = Math.max(totalOffset, aboveTotal)
            }
          }
          offsetMap.set(key, totalOffset + 0)
        })
      })

      this.cascadeOffsetMap = offsetMap
    },
    // 获取当前块对应当前页的原始index-用作点击定位
    getBIndex (blk) {
      return blk.index
    },
    // 块点击
    blkClick (trsId) {
      this.currentId = trsId
      // 获取当前块相对容器视口距离
      const container = document.querySelector('.trs-scroll-box');
      const self = document.querySelector(`#${trsId}`);
      const parent = self.parentElement;
      const offsetTop = self.offsetTop + parent.offsetTop - container.offsetTop;
      let selfTop = offsetTop - container.scrollTop
      this.$emit('blkClick', `#${trsId.replace('trs', 'orig')}`,false,selfTop)
    },
    // 监听滚动
    onScroll (e) {
      if(this.isProgrammaticScroll){
        this.isProgrammaticScroll = false
        // 程序触发滚动不执行以下逻辑
        return
      }
      // console.log(e.target.scrollTop, '译文滚动距离');
      let el = e.target
      let heights = []
      this.layout.pdf_info.forEach((pInfo, pIdx) => {
        let h = this.$refs[`pageRef${pIdx}`][0].offsetHeight
        heights.push(h)
      })
      heights.forEach((h,hindex)=>{
        if(hindex === 0){
          heights[0] = h
        }else{
          heights[hindex] = h + heights[hindex-1]
        }
      })
      // console.log(heights,'342222');
      let pageIndex = heights.findIndex(h=>{
        return el.scrollTop <= h - 200
      })
      this.$emit('trsScroll',pageIndex+1)
      // this.$emit('blkClick', `#pdf-page-${pageIndex+1}`)
    }
  },

  directives: {
    scaleKatex: {
      inserted (el, binding) {
        renderKatexOrImage(el, binding.value)
      },
      update (el, binding) {
        const { content, imagePath } = binding.value || {}
        const old = binding.oldValue || {}
        if (content !== old.content || imagePath !== old.imagePath) {
          renderKatexOrImage(el, binding.value)
        }
      }
    },
    scaleImg: {
      inserted (el) {
        el.onload = () => scaleToBox(el, el.parentElement)
      }
    },
    scaleTable: {
      inserted (el, binding) {
        el.innerHTML = binding.value
        scaleToBox(el, el.parentElement)
      }
    }
  }
}
function renderKatexOrImage (el, { content, imagePath }) {
  el.innerHTML = ''
  let success = false

  // ✅ 优先尝试渲染公式
  if (content && content.trim()) {
    try {
      const html = katex.renderToString(content, { displayMode: true, throwOnError: true })
      el.innerHTML = html
      scaleToBox(el, el.parentElement)
      success = true
    } catch (e) {
      console.warn('[KaTeX] 公式渲染失败，降级为图片:', e.message)
    }
  }

  // ✅ 公式失败或为空，且图片存在 → 降级展示图片
  if (!success && imagePath) {
    const img = document.createElement('img')
    img.src = imagePath
    img.style.maxWidth = '100%'
    img.style.height = 'auto'
    img.onload = () => scaleToBox(el, el.parentElement)
    el.appendChild(img)
  }

  // ✅ 都失败 → 空状态（可选提示）
  if (!success && !imagePath) {
    el.innerHTML = '<span style="color:#999;font-size:12px;">公式不可用</span>'
  }
}
function scaleToBox (inner, box) {
  const pw = box.clientWidth
  const ph = box.clientHeight
  const iw = inner.offsetWidth || inner.naturalWidth || inner.clientWidth
  const ih = inner.offsetHeight || inner.naturalHeight || inner.clientHeight
  if (!iw || !ih) return
  const scale = Math.min(pw / iw, ph / ih, 1)
  inner.style.transformOrigin = '0 0'
  inner.style.transform = `scale(${scale}) translate(${(pw / scale - iw) / 2}px, ${(ph / scale - ih) / 2}px)`
}
</script>

<style lang="less">
* {
  font-family: "Microsoft YaHei", "微软雅黑", sans-serif;
}

.katex-inline .katex {
  line-height: 0.5 !important;

  .katex-html {
    font-size: clamp(13px, 0.5em, 40px);
  }
}

.katex-display {
  margin: 0;
  margin-right: 40px;

  .tag {
    transform: translateX(100%);
  }
}
</style>

<style scoped lang="less">
.trs-scroll-box {
  // padding: 20px;
  background: #fafafa;
  overflow: auto;
  height: 100%;

  /deep/ table {
    border-collapse: collapse;
    border: 1px solid #666;
    width: 100% !important;

    th,
    td {
      border: 1px solid #666;
      padding: 4px 6px;
    }

    th {
      background: #f0f0f0;
      font-weight: bold;
    }
  }
}

.scale-wrapper {
  position: absolute;
  // overflow: hidden;
}

.scale-inner {
  display: inline-block;
  transform-origin: 0 0;
}

.text-block {
  padding-bottom: 2px;
}

.text-block,
.scale-wrapper {

  .inline-wrapper,
  .scale-inner {
    cursor: pointer;

    &:hover {
      background: rgba(135, 206, 250, .4);
      border-radius: 10px;
      // color: #fff;
    }
  }

  .active_box {
    background: rgba(135, 206, 250, .4);
    border: 2px solid #003d82;
  }
}
</style>